/***************************************************************************
                          funcs.cpp  -  description
                             -------------------
    begin                : Tue Aug 27 2002
    copyright            : (C) 2002 by Michiel Visser
    email                : 
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include "expr.h"
#include "parser.h"
//sdk stuff
#include<Windows.h>
#include <time.h>
#include <math.h>
#include <sys/stat.h>
#include "random.h"

//#include <unistd.h>

#define CHECKTRUE(x) { if (!(x)) { _TRACE; } }
#define CHECKFALSE(x) { if ((x)) { _TRACE; } }

namespace Expression
{
 
#define FUNC_EXPR(x) 	IFDEBUG(void x##Expr::printTyle() const {printf(#x); } ) void x##Expr::evaluateToCopy(void *space)

FUNC_EXPR(eval)
{ Object *r=session.getStackVar(0).val->value();
	EPtr res;
  TyleParser(r->toString(), res, space);
  if (!!res)
		new (space) ExprObject(res);
}

FUNC_EXPR(load)
{ Object *r=session.getStackVar(0).val->value();
	int begin=0, length=0;
	bool hasLength = false; 
	String filename;
	if (r->getTypeNum()==TOT_List)
	{	OListPtr ol(r->getConstList());
		int oll=ol->length();
		filename=(*ol)[0]->toString();
		if (oll>1) begin=(*ol)[1]->toInt();
		if (oll>2 && (*ol)[2]->value()->getTypeNum()!=TOT_Null) { hasLength=true; length=(*ol)[2]->toInt(); }
	}
	else
		filename=r->toString();
  FILE *f=fopen(filename,"rb");
  if (f)
  { fseek(f,0,SEEK_END); int l=ftell(f);
	if (begin<0) begin+=l;
	if (begin>l) begin=l;
	fseek(f,begin,SEEK_SET);
	l-=begin;
	if (hasLength) 
	{	if (length<0) { l+=length; if (l<0) l=0; }
  		else if (length<l) l=length;
	}
    String s(l);
    fread((char *)s, l,1,f);
    fclose(f);
    new (space) StringObject(s);
    return;
  }
  new (space) NullObject();
}

FUNC_EXPR(save)
{ Object *r=session.getStackVar(0).val->value();
	int begin=0;
	bool truncate=true;
	String filename;
	if (r->getTypeNum()==TOT_List)
	{	const OList &ol=r->getList();
		int oll=ol.length();
		filename=ol[0]->toString();
		if (oll>1) { truncate=false; begin=ol[1]->toInt(); }
	}
	else
		filename=r->toString();
	FILE *f=truncate?0:fopen(filename,"r+b");
	if (!f) f=fopen(filename,"wb");
	else
	{	if (begin<0) fseek(f,begin+1,SEEK_END);
		else fseek(f,begin,SEEK_SET);
	}
	if (f)
	{
	  String s=session.getStackVar(1).val->toString();
    fwrite((char *)s, s.length(),1,f);
	  fclose(f);
  }
  new (space) NullObject();
}

FUNC_EXPR(truncate)
{ Object *r=session.getStackVar(0).val->value();
	const OList &ol=r->getList();
	if (ol.length()>=2)
	{	//truncate(ol[0]->toString(),ol[1]->toInt());

		HANDLE file = CreateFileA( ol[0]->toString(), GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, 0, 0 );
		if (file != INVALID_HANDLE_VALUE)
		{
	    if (SetFilePointer( file, ol[1]->toInt(), NULL, FILE_BEGIN ))
			{
				SetEndOfFile (file);
			}
			CloseHandle( file );
		}

	}
  new (space) NullObject();
}

static int timeMode; // 0=local, 1=gmt

static void getTime(const time_t t, void *space)
{	OList l;
  int *bt=(int *)(timeMode ? gmtime(&t) : localtime(&t));
	bt[4]+=1;
	bt[5]+=1900;
  for(int i=0; i<6;i++)
  {	l.prepend(NEW_OBJ IntegerObject(bt[i]));
  }
  new (space) ListObject(l);
}

FUNC_EXPR(info)
{ Object * r=session.getStackVar(0).val;
	struct _stat buffer;
	int begin=0, length=0,base=0;
  if(_stat((char *)r->toString(),&buffer)==0) //length,permission,date
  { 
		OList l;
	  l.prepend(NEW_OBJ IntegerObject(buffer.st_mode));
	  l.prepend(NEW_OBJ IntegerObject(buffer.st_size));
	  l.prepend(ao());
	  getTime(buffer.st_mtime,*l);
	  new (space) ListObject(l);
	  return;
  }
  new (space) NullObject();
}

FUNC_EXPR(getDir)
{ Object * r=session.getStackVar(0).val;
	struct _stat buffer;
	int begin=0, length=0,base=0;
	_WIN32_FIND_DATAA data;
	String s(r->toString(),"\\*");
	HANDLE h=FindFirstFileA((char *)s,&data);
	OList l;
	if (h!=INVALID_HANDLE_VALUE)
	{
		do
		{	if (data.cFileName[0]!='.'||((data.cFileName[1]!='.'||data.cFileName[2]!=0)&&data.cFileName[1]!=0))
	  		l.prepend(NEW_OBJ StringObject(data.cFileName));
		} while(FindNextFileA(h,&data));
		FindClose(h);
	}
	new (space) ListObject(l);
}

FUNC_EXPR(sleep)
{
  int i=session.getStackVar(0).val->toInt();
  session.leave();
  Sleep(i);
  session.enter();
#ifdef MALLOC_COUNT
  new (space) IntegerObject(mcounts.variable+mcounts.block);
  return;
#else
	new (space) NullObject();
#endif
}

FUNC_EXPR(random)
{
IFDEBUG( dbgvars[session.getStackVar(0).val->toInt()]=session.getStackVar(1).val->toInt();)
	new (space) IntegerObject(RNG());
}

FUNC_EXPR(setCb)
{
		String s (session.getStackVar(0).val->toString() );
		HANDLE nd = GlobalAlloc(GMEM_MOVEABLE|GMEM_DDESHARE, strlen(s)+2);
	  LPSTR ns = (LPSTR)GlobalLock(nd);
	  strcpy(ns,s);
	  GlobalUnlock(nd);
		OpenClipboard(NULL);
	  EmptyClipboard();       
	  SetClipboardData(/*CF_OEMTEXT*/ CF_TEXT, nd); 
	  CloseClipboard(); 
	  new (space) NullObject();
}

FUNC_EXPR(getCb)
{
		HANDLE nd;
		LPSTR ns; 
		OpenClipboard(NULL);
	  nd=GetClipboardData(CF_TEXT);
    if (nd)
    {
  	  ns = (LPSTR)GlobalLock(nd);
  	  String s((char *)ns);
  	  GlobalUnlock(nd);
  	  new (space) StringObject(s);
  	}
  	else
  	{ new (space) StringObject("");
  	}
 	  CloseClipboard(); 
}

FUNC_EXPR(sys)
{
	Object * r=session.getStackVar(0).val;
	SECURITY_ATTRIBUTES saAttr; 
	saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); 
  saAttr.bInheritHandle = TRUE; 
  saAttr.lpSecurityDescriptor = NULL; 

	HANDLE hChildStdoutRd, hChildStdoutWr, hChildStdinRd,hChildStdinWr,
		hChildStdoutRdDup, hChildStdinWrDup;
	CreatePipe(&hChildStdoutRd, &hChildStdoutWr, &saAttr, 0);
	DuplicateHandle(GetCurrentProcess(), hChildStdoutRd,GetCurrentProcess(), &hChildStdoutRdDup, 0, FALSE, DUPLICATE_SAME_ACCESS);
	CloseHandle(hChildStdoutRd);
	CreatePipe(&hChildStdinRd, &hChildStdinWr, &saAttr, 0);
	DuplicateHandle(GetCurrentProcess(), hChildStdinWr, GetCurrentProcess(), &hChildStdinWrDup, 0, FALSE, DUPLICATE_SAME_ACCESS);
	CloseHandle(hChildStdinWr); 

	STARTUPINFO si;
	memset(&si,0,sizeof(si));
	si.cb = sizeof(si);
	si.hStdError = hChildStdoutWr;
	si.hStdOutput = hChildStdoutWr;
	si.hStdInput = hChildStdinRd;
	si.dwFlags |= STARTF_USESTDHANDLES;

	PROCESS_INFORMATION pi;
	String command;
	if (r->getTypeNum()==TOT_List)
	{	const OList &ol=r->getList();
		if (ol.length()>1)
		{ command=ol[0]->toString();	
			DWORD dwWritten;
			String in( ol[1]->toString() );
			WriteFile(hChildStdinWrDup, (char *)in, in.length()+1, &dwWritten, NULL);
		}
		else
		  command=r->toString();	
	}
	else
	 command=r->toString();	

	 
	if(!CreateProcess(0, (char *)command,0,0,TRUE,CREATE_NO_WINDOW,0,0,/*in*/&si,/*out*/&pi) )
	{	CloseHandle(hChildStdoutWr);
		CloseHandle(hChildStdinRd);
		CloseHandle(hChildStdinWrDup);
		CloseHandle(hChildStdoutRdDup);  
		{ new (space) NullObject(); return; }
	}
	ListObject l; 
	OList::P pl(&*l);
	session.leave();
  for(;;)
	{ DWORD result=WaitForSingleObject(pi.hProcess, 1);
		DWORD numberOfBytes;
		PeekNamedPipe(hChildStdoutRdDup,0,0,0,&numberOfBytes,0);
    if (numberOfBytes)
		{	session.enter();
		  {
        String buf(numberOfBytes);
  			DWORD dwRead;
  			ReadFile( hChildStdoutRdDup, (char *)buf, numberOfBytes, &dwRead, NULL ); 
  			pl->prepend(NEW_OBJ StringObject(buf));
  			pl++;
      }
    	 session.leave();
		}
		if (result!=WAIT_TIMEOUT) break;
	}
	DWORD status;
	GetExitCodeProcess(pi.hProcess, &status);
	CloseHandle(hChildStdoutWr);
	CloseHandle(hChildStdinRd);
	CloseHandle(hChildStdinWrDup);
  CloseHandle(hChildStdoutRdDup);  	
	CloseHandle(pi.hProcess);
	CloseHandle(pi.hThread);

  session.enter();
	OList rlist;
  rlist.prepend(NEW_OBJ StringObject(l.toString()));
  rlist.prepend(NEW_OBJ IntegerObject(status));
  new (space) ListObject(rlist);
}

FUNC_EXPR(puts)
{ printf("%s",(char *) ( session.getStackVar(0).val->toString() ) );
  new (space) NullObject();
}

FUNC_EXPR(gets)
{ String str(16384);
  session.leave();
  char *res=gets((char *)str);
  session.enter();
	if (res)
  	new (space) StringObject((char *)str);
	else
	  new (space) NullObject();
}

FUNC_EXPR(toAscii)
{ new (space) IntegerObject( (unsigned char)((session.getStackVar(0).val->toString())[0]) );
}

FUNC_EXPR(fromAscii)
{ char s[1]; s[0]=(char)(session.getStackVar(0).val->toInt());
  new (space) StringObject( s, 1 );
}

FUNC_EXPR(winGetFirst)
{ Object * p=session.getStackVar(0).val->value();
  new (space) IntegerObject((int)GetTopWindow(p->vtable == &vtable_Null ? NULL: (HWND)(p->toInt() )) );
}

FUNC_EXPR(winGetNext)
{ new (space) IntegerObject((int)GetNextWindow((HWND)(session.getStackVar(0).val->toInt()), GW_HWNDNEXT ) );
}

FUNC_EXPR(winGetParent)
{	new (space) IntegerObject((int)GetParent((HWND)(session.getStackVar(0).val->toInt()) ) );
}

FUNC_EXPR(winSendKey)
{ 
	OListPtr l( session.getStackVar(0).val->getConstList() );
  int ll=l->length();
  if (ll)
    keybd_event( (*l)[0]->toInt(), ll<=2?0:(*l)[2]->toInt(), ll<=1?0:(*l)[1]->toInt(), 0); 
	//PostMessage( (HWND)(l[0]->toInt()), l[1]->toInt(), ll<=2?0:l[2]->toInt(), ll<=3?0:l[3]->toInt() );
  
  new (space) NullObject();
}

FUNC_EXPR(winGetKeyState)
{ 
	new (space) IntegerObject( GetAsyncKeyState ( session.getStackVar(0).val->toInt() ) );
}

FUNC_EXPR(winGetIdleTime)
{	
	LASTINPUTINFO info;
	info.cbSize = sizeof(LASTINPUTINFO);
	if (GetLastInputInfo(&info) )
		new (space) IntegerObject( GetTickCount() - info.dwTime );
	else
		new (space) NullObject();
}

FUNC_EXPR(winSendMouseEvent) // motion=0x8000, absolute=0x10000000, normalised=0x20000000
{
	OListPtr l ( session.getStackVar(0).val->getConstList() );
  int ll=l->length();
	POINT p = {0,0};
	int f=(*l)[0]->toInt() ^ 0x8000; // default must be absolute instead of relative.
	if (ll>2)
	{	f |= 1;
		int x=(*l)[1]->toInt();
		int y=(*l)[2]->toInt();
		if ( (f&0x10000000)==0 ) // relative
		{	ClientToScreen(GetForegroundWindow(),&p);
		}
		if (x<0||y<0)
		{	RECT r;
			GetClientRect((f&0x10000000) ? GetDesktopWindow() : GetForegroundWindow(),&r);
			if (x<0)
				p.x+=r.right-r.left;
			if (y<0)
				p.y+=r.bottom-r.top;
		}
		if ( (f&0x20000000)==0 ) { p.x += x; p.y += y; } // not normalised coords, so add coords before normalisation
		RECT r;
		GetClientRect(GetDesktopWindow(),&r);
		p.x = (p.x*0x10000)/(r.right-r.left);
		p.y = (p.y*0x10000)/(r.bottom-r.top);
		if ( (f&0x20000000)!=0 ) { p.x += x; p.y += y; } // normalised, so add after normalisation
	}
  mouse_event( f, p.x, p.y, ll<=3?0:(*l)[3]->toInt(), 0 );
  new (space) NullObject();
}

FUNC_EXPR(winGetMousePos)
{
	POINT p;
	GetCursorPos(&p);
	if ( session.getStackVar(0).val->toInt() == 0 ) ScreenToClient(GetForegroundWindow(), &p);
	OList l;
  l.prepend(NEW_OBJ IntegerObject(p.y)); 
  l.prepend(NEW_OBJ IntegerObject(p.x));
  new (space) ListObject(l);
}

FUNC_EXPR(winActivate)
{ 
	HWND w = (HWND) (session.getStackVar(0).val->toInt());
	WINDOWPLACEMENT wp;
	wp.length=sizeof(wp);
	GetWindowPlacement(w,&wp);
	if (wp.showCmd==SW_SHOWMINIMIZED)
		ShowWindow(w,  SW_SHOWNORMAL );
	SetWindowPos(w,HWND_TOPMOST,0,0,0,0,SWP_NOMOVE|SWP_NOSIZE);
	SetForegroundWindow(w);
	SetForegroundWindow(w); // must be done twice
 	SetWindowPos(w,HWND_NOTOPMOST,0,0,0,0,SWP_NOMOVE|SWP_NOSIZE);
  new (space) NullObject();
}
FUNC_EXPR(winGetPixel)
{
	OListPtr l( session.getStackVar(0).val->getConstList() );
   int len=l->length();
   if (len<2) { new (space) NullObject(); return; }
   HWND win = (len>2 && (*l)[2]->vtable != &vtable_Null) ? (HWND)(*l)[2]->toInt() : GetForegroundWindow();
   HDC hdc=GetDC(win);
   int c=(int)GetPixel(hdc,(*l)[0]->toInt(),(*l)[1]->toInt());
   ReleaseDC(win,hdc);
   new (space) IntegerObject(c);
}

FUNC_EXPR(winGetActive)
{ 
  new (space) IntegerObject( (int)(GetForegroundWindow()) );
}

FUNC_EXPR(winGetName)
{ 
  char str[1024];
  GetWindowText((HWND) (session.getStackVar(0).val->toInt() ), str,sizeof(str));	
  new (space) StringObject(str);
}
  
FUNC_EXPR(getDateTime)
{ time_t t=time(0);
  getTime(t,space);
}

FUNC_EXPR(setTimeMode)
{ 
  timeMode = session.getStackVar(0).val->toInt();
  new (space) NullObject();
}

FUNC_EXPR(getTickCount)
{ new (space) IntegerObject((int)GetTickCount());
}

FUNC_EXPR(hash)
{ new (space) IntegerObject(session.getStackVar(0).val->hash() );
}

FUNC_EXPR(getGlobals)
{ session.getGlobalNamesObject()->duplicateIn(space);
}

FUNC_EXPR(getTypes)
{ session.m_typeNames.m_names->duplicateIn(space);
}

void newThread(LPVOID lpVoid)
{ 
  Thread *t=(Thread *)lpVoid;
  session.enter();
  session.pushStack(NEW_OBJ IntegerObject((int)t->m_father));
  t->m_func->evaluateToVoid();
  session.popStack();
  session.m_curThread = 0;
  delete t;
  session.leave();
  ExitThread(0);
}

FUNC_EXPR(spawn) //evalstr,priority
{
  unsigned long   threadId;
  int priority=0;
  Object * func=session.getStackVar(0).val->value();
	if (func->getTypeNum()==TOT_List)
	{ OListPtr l ( func->getConstList() );
    int ll=l->length();
    if (ll>0) func=(*l)[0];
    if (ll>1) priority=(*l)[1]->toInt();
  }
  if (func->getTypeNum() == TOT_Expr)
  { 
    //SHOW(x,GetThreadPriority(GetCurrentThread())); todo, set main thread prio
    Thread *t = new Thread( session.m_curThread, func->copy() );
    HANDLE h=CreateThread(NULL,0x20000,(LPTHREAD_START_ROUTINE)newThread,t,CREATE_SUSPENDED,&threadId);
    SetThreadPriority(h,priority);
    t->setVars(h, threadId);
    new (space) IntegerObject( (int)t );
    ResumeThread(h);
    session.schedule();
    return;
  }
  new (space) NullObject();
}

FUNC_EXPR(putt)
{ Thread *t=(Thread *)session.getStackVar(0).val->toInt();
  if (session.m_threads.contains(t))
  { Object * o=session.getStackVar(1).val;
    o->duplicateIn(t->m_incoming.end().prependalloc(OBJ_SIZE));
    t->m_sem.release();
  }
  session.schedule();
  new (space) NullObject();
}

FUNC_EXPR(gett)
{ Thread *t=session.m_curThread;
  if (!t->m_incoming)
  { int timeout;
    {
      Object * o=session.getStackVar(0).val->value();
      timeout=o->vtable == &vtable_Null?INFINITE:o->toInt();
    }
    session.leave();
    t->m_sem.claim(timeout);
    session.enter();
    if (!t->m_incoming)
      { new (space) NullObject(); return; }    
  }
  else
    session.m_curThread->m_sem.claim(0); //try to claim to get event when list is empty
  t->m_incoming->duplicateIn(space);
  t->m_incoming.removeone();
}

FUNC_EXPR(operateMath)
{ 
	float r = session.getStackVar(1).val->toFloat();
	switch(session.getStackVar(0).val->toInt())
	{
	case 0:  r=sqrt (r); break;
	case 1:  r=exp  (r); break;
	case 2:  r=log  (r); break;
	case 3:  r=sin  (r); break;
	case 4:  r=cos  (r); break;
	case 5:  r=tan  (r); break;
	case 6:  r=asin (r); break;
	case 7:  r=acos (r); break;
	case 8:  r=atan (r); break;
	case 9:  r=floor(r); break;
	case 10: r=ceil (r); break;
	case 11: r=tanh (r); break;
	}
	new (space) FloatObject( r );
}

#ifdef DEBUGLOGGING
FUNC_EXPR(getVarLog)
{ 
	session.getLog(space);
}
FUNC_EXPR(setVarLogSize)
{ 
	session.setLogSize(session.getStackVar(0).val->toInt());
	new (space) NullObject();
}
#endif
	
} // namespace
